#include <dummy.h>

/* *****************************************************************

   Download latest Blinker library here:
   https://github.com/blinker-iot/blinker-library/archive/master.zip


   Blinker is a cross-hardware, cross-platform solution for the IoT.
   It provides APP, device and server support,
   and uses public cloud services for data transmission and storage.
   It can be used in smart home, data monitoring and other fields
   to help users build Internet of Things projects better and faster.

   Make sure installed 2.7.4 or later ESP8266/Arduino package,
   if use ESP8266 with Blinker.
   https://github.com/esp8266/Arduino/releases

   Make sure installed 1.0.4 or later ESP32/Arduino package,
   if use ESP32 with Blinker.
   https://github.com/espressif/arduino-esp32/releases

   Docs: https://diandeng.tech/doc


 * *****************************************************************

   Blinker 库下载地址:
   https://github.com/blinker-iot/blinker-library/archive/master.zip

   Blinker 是一套跨硬件、跨平台的物联网解决方案，提供APP端、设备端、
   服务器端支持，使用公有云服务进行数据传输存储。可用于智能家居、
   数据监测等领域，可以帮助用户更好更快地搭建物联网项目。

   如果使用 ESP8266 接入 Blinker,
   请确保安装了 2.7.4 或更新的 ESP8266/Arduino 支持包。
   https://github.com/esp8266/Arduino/releases

   如果使用 ESP32 接入 Blinker,
   请确保安装了 1.0.4 或更新的 ESP32/Arduino 支持包。
   https://github.com/espressif/arduino-esp32/releases

   文档: https://diandeng.tech/doc


 * *****************************************************************/

#define BLINKER_WIFI
#define BLINKER_WITHOUT_SSL
#define BLINKER_MIOT_MULTI_OUTLET

#define LED_BUILTIN 5

#include <Blinker.h>
#include <Ticker.h>

#define BRIDGE_1 "a1afbc3d6548"



BlinkerBridge BridgeDevice1(BRIDGE_1);

#define BLINK_TIME_MAX 1
#define LED_HOLD_MS 300

char auth[] = "a1ebc91b385c";
char ssid[] = "will-master";
char pswd[] = "td1234567";

BlinkerButton Button1("btn-abc");
BlinkerButton ButtonSleep("pc-sleep");
BlinkerButton ButtonBootup("pc-bootup");

// timer blink
Ticker flipper;
int fliperCnt = 0;

void flip() {
  BLINKER_LOG("flip START -- fliperCnt : ", fliperCnt);
  if (fliperCnt < 1) {
    flipper.detach();
    return;
  }

  int fliperState = digitalRead(LED_BUILTIN);
  digitalWrite(LED_BUILTIN, !fliperState);
  fliperCnt -= 1;
  BLINKER_LOG("flip END -- fliperCnt : ", fliperCnt, ", fliperState: ", fliperState);  
}

#define SLOT_CNT 1
bool oState[SLOT_CNT] = { false };

void blink_led(int blink_times, int hold_ms) {
  fliperCnt = blink_times * 2 - 1;
  digitalWrite(LED_BUILTIN, HIGH);  
  flipper.attach_ms(hold_ms, flip);
  BLINKER_LOG("fliperCnt : ", fliperCnt);
}

void button1_callback(const String& state) {
  BridgeDevice1.print("{\"btn-sleep\":\"bridge\"}");
  blink_led(BLINK_TIME_MAX, LED_HOLD_MS);
}

// wol

const unsigned int portListen = 5612;
const unsigned int portBroadcast = 5611;
WiFiUDP udp;

const int maxTargets = 1;
byte targetMAC[maxTargets][6] = { 0xE0, 0x51, 0xD8, 0x13, 0xA7, 0x7C };

IPAddress getSubnetBroadcastAddress() {
  IPAddress mask = WiFi.subnetMask();
  IPAddress ip = WiFi.localIP();
  IPAddress broadcast;
  for (int i = 0; i < 4; i++) {
    broadcast[i] = ip[i] | ~mask[i];
  }
  return broadcast;
}

void bytesToStr(char* out, const byte bytes[], int count) {
  for (int i = 0; i < count; i++) {
    if (i > 0) {
      *out++ = ':';
    }
    const byte current = bytes[i];
    *out++ = lowerByteToHex(current >> 4);
    *out++ = lowerByteToHex(current & 0x0F);
  }
  *out++ = '\0';
}

char lowerByteToHex(byte b) {
  if (b < 0xA) {
    return (char)b + '0';
  }
  return (char)b - 0xA + 'A';
}

void sendMagicPacket(int target) {
  const byte header[6]{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
  char strBuf[6 * 3];

  udp.beginPacket(getSubnetBroadcastAddress(), portBroadcast);
  udp.write(header, 6);

  for (int i = 0; i < 16; i++) {
    udp.write(targetMAC[target], 6);
  }

  udp.endPacket();
}

void button_bootup_cb(const String& state) {
  sendMagicPacket(0);
  blink_led(BLINK_TIME_MAX, LED_HOLD_MS);
}

void heartbeat() {
  blink_led(BLINK_TIME_MAX, LED_HOLD_MS);
}



void miotPowerState(const String& state, uint8_t num) {
  blink_led(BLINK_TIME_MAX, LED_HOLD_MS);
  if (state == BLINKER_CMD_ON) {
    digitalWrite(LED_BUILTIN, HIGH);

    BlinkerMIOT.powerState("on", num);
    BlinkerMIOT.print();
    sendMagicPacket(0);

    oState[num] = true;
  } else if (state == BLINKER_CMD_OFF) {
    digitalWrite(LED_BUILTIN, LOW);

    BlinkerMIOT.powerState("off", num);
    BlinkerMIOT.print();
    BridgeDevice1.print("{\"btn-sleep\":\"bridge\"}");

    oState[num] = false;

    if (num == 0) {
      for (uint8_t o_num = 0; o_num < SLOT_CNT; o_num++) {
        oState[o_num] = false;
      }
    }
  }
}

void miotQuery(int32_t queryCode, uint8_t num) {
  BLINKER_LOG("AliGenie Query outlet: ", num, ", codes: ", queryCode);
  blink_led(BLINK_TIME_MAX, LED_HOLD_MS);

  switch (queryCode) {
    case BLINKER_CMD_QUERY_ALL_NUMBER:
      BlinkerMIOT.powerState(oState[num] ? "on" : "off", num);
      BlinkerMIOT.print();
      BridgeDevice1.print("{\"btn-sleep\":\"bridge\"}");
      break;
    case BLINKER_CMD_QUERY_POWERSTATE_NUMBER:
      BlinkerMIOT.powerState(oState[num] ? "on" : "off", num);
      BlinkerMIOT.print();
      break;
    default:
      BlinkerMIOT.powerState(oState[num] ? "on" : "off", num);
      BlinkerMIOT.print();
      break;
  }
}


void bridge1Read(const String& data) {
  BLINKER_LOG("BridgeDevice1 readString: ", data);

  // must print Json data
  BridgeDevice1.print("{\"hello\":\"bridge\"}");
}

void setup() {
  // 初始化串口
  Serial.begin(115200);
  BLINKER_DEBUG.stream(Serial);
  BLINKER_DEBUG.debugAll();

  // 初始化有LED的IO
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);
  // 初始化blinker
  Blinker.begin(auth, ssid, pswd);
  Blinker.attachHeartbeat(heartbeat);

  Button1.attach(button1_callback);
  ButtonBootup.attach(button_bootup_cb);
  BlinkerMIOT.attachPowerState(miotPowerState);
  BlinkerMIOT.attachQuery(miotQuery);
  BridgeDevice1.attach(bridge1Read);
  blink_led(BLINK_TIME_MAX, LED_HOLD_MS);
}

void loop() {
  Blinker.run();
}
